#include <stdio.h>
#include <vector>

#include <emscripten/val.h>
#include <emscripten/bind.h>
#include <emscripten/fetch.h>

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>

#include "utils/sha1.hpp"


using namespace cv;
using namespace std;
using namespace emscripten;


thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");

double *get1XArrayFormHEAP(val arr) {
	int len = arr["length"].as<int>();
	double *ret = new double[len];
	val module = val::global("Module");
	int ptr = (int)ret / sizeof(double);
	module["HEAPF64"].call<val>("set", arr, val(ptr));
	return ret;
}

// val get1XArrayToMemory(double *arr, int len){
// 	return val(typed_memory_view(len, arr));
// }

val matToJS(Mat mat) {
  int cols = mat.cols;
  int rows = mat.rows;
  int channels = mat.channels();
  size_t dataSize = cols * rows * channels;
  double* array = new double[dataSize];
  mat.convertTo(Mat(rows, cols, CV_64FC(channels), array), CV_64F);
  val js_result = Uint8ClampedArray.new_(typed_memory_view(dataSize, array));
  // val js_result = val(typed_memory_view(dataSize, array));
  free(array);
  return js_result;
}

Mat jsToMat(val js_arr, int rows, int cols) {
  double *data = get1XArrayFormHEAP(js_arr);
  Mat mat(rows, cols, CV_64FC4, data);
  Mat dst;
  mat.convertTo(dst, CV_8UC4);
  free(data);
  return dst;
}

// 4通道拆分为bgr和alpha
void src4split(Mat src, Mat img, Mat alpha) {
  vector<Mat> channels;
  split(src, channels);
  merge(vector<Mat>{channels[0], channels[1], channels[2]}, img);
  merge(vector<Mat>{channels[3]}, alpha);
}

Mat cvGaussianBlur(Mat src, int kernel_size = 9) {
    double sigma = 0;    // 标准差，如果为0，则根据核大小自动计算
    // 创建高斯核
    cv::Mat kernel = cv::getGaussianKernel(kernel_size, sigma, CV_64F);
    // 进行高斯滤波
    cv::Mat dst;
    cv::sepFilter2D(src, dst, -1, kernel, kernel, Point(-1, -1));
    return dst;
}

Mat gamma_correction(Mat img, int value = 30){
    Mat imgw = img.clone();
    cvtColor(imgw, imgw, COLOR_BGR2RGB);//这里要转成RGB格式进行后续查找替换

    // 创建用于映射的查找表
    vector<uchar> lookup(256);
    // 创建一个向量用于计算中间色调的增益
    vector<float> midtones_add(256);
    // 计算中间色调的增益
    for (int i = 0; i < 256; i++) {
        midtones_add[i] = 0.667f * (1 - ((i - 127.0f) / 127) * ((i - 127.0f) / 127));
    }

    // 创建查找表
    for (int i = 0; i < 256; i++) {
        int red = i + static_cast<int>(value * midtones_add[i]);
        red = max(0, min(0xff, red));
        lookup[i] = static_cast<uchar>(red);
    }

    // 应用查找表调整图像颜色
    for (int r = 0; r < img.rows; r++) {
        for (int c = 0; c < img.cols; c++) {
              imgw.at<Vec3b>(r, c)[0] = lookup[imgw.at<Vec3b>(r, c)[0]];
              imgw.at<Vec3b>(r, c)[1] = lookup[imgw.at<Vec3b>(r, c)[1]];
              imgw.at<Vec3b>(r, c)[2] = lookup[imgw.at<Vec3b>(r, c)[2]];
        }
    }
    cv::cvtColor(imgw, imgw, COLOR_RGB2BGR);
    return imgw;
}

bool usable = false;

extern "C" {
  
  std::string auth() {
    time_t t = time(0); 
    char tmp[32]={NULL};
    // "%Y-%m-%d %H:%M:%S"
    strftime(tmp, sizeof(tmp), "%Y-%m-%d",localtime(&t));
    std::string str(tmp);
    usable = true;
    str = sha1("用户名" + str);
    return str;
  }

  bool isUsable() {
    return usable;
  }

  val asmGamma(val js_arr, int rows, int cols, int gamma) {
    Mat src4 = jsToMat(js_arr, rows, cols);
    Mat img(src4.size(), CV_8UC3);
    Mat alpha(src4.size(), CV_8UC1);
    src4split(src4, img, alpha);
    Mat ret = gamma_correction(img, gamma);
    Mat dst;
    merge(vector<Mat>{ret, alpha}, dst);
    return matToJS(dst);
  }

  val asmBlur(val js_arr, int rows, int cols, int kernel_size = 9) {
    Mat src = jsToMat(js_arr, rows, cols);
    Mat dst = cvGaussianBlur(src, kernel_size);
    return matToJS(dst);
  }
  
  std::string join(std::string a, std::string b) {
    return a + ' ' + b;
  }

}


EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("join", join);
  emscripten::function("asmBlur", asmBlur);
  emscripten::function("asmGamma", asmGamma);
  emscripten::function("auth", auth);
  emscripten::function("isUsable", isUsable);
}
